/** * Copyright (c) 2010-2017 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.binding.keba.handler; import static org.openhab.binding.keba.KebaBindingConstants.*; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.PortUnreachableException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.DatagramChannel; import java.nio.channels.NotYetConnectedException; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.TimeZone; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.apache.commons.lang.StringUtils; import org.eclipse.smarthome.core.library.types.DateTimeType; import org.eclipse.smarthome.core.library.types.DecimalType; import org.eclipse.smarthome.core.library.types.IncreaseDecreaseType; import org.eclipse.smarthome.core.library.types.OnOffType; import org.eclipse.smarthome.core.library.types.PercentType; import org.eclipse.smarthome.core.thing.ChannelUID; import org.eclipse.smarthome.core.thing.Thing; import org.eclipse.smarthome.core.thing.ThingStatus; import org.eclipse.smarthome.core.thing.ThingStatusDetail; import org.eclipse.smarthome.core.thing.binding.BaseThingHandler; import org.eclipse.smarthome.core.types.Command; import org.eclipse.smarthome.core.types.RefreshType; import org.eclipse.smarthome.core.types.State; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonParser; /** * The {@link KeContactP20Handler} is responsible for handling commands, which * are sent to one of the channels. * * @author Karel Goderis - Initial contribution */ public class KeContactP20Handler extends BaseThingHandler { public static final String IP_ADDRESS = "ipAddress"; public static final String POLLING_REFRESH_INTERVAL = "refreshInterval"; public static final int CONNECTION_REFRESH_INTERVAL = 100; public static final int REPORT_INTERVAL = 2000; public static final int PING_TIME_OUT = 3000; public static final int BUFFER_SIZE = 1024; public static final int REMOTE_PORT_NUMBER = 7090; public static final int LISTENER_PORT_NUMBER = 7090; private Logger logger = LoggerFactory.getLogger(KeContactP20Handler.class); private Selector selector; private DatagramChannel datagramChannel = null; protected SelectionKey datagramChannelKey = null; protected DatagramChannel listenerChannel = null; protected SelectionKey listenerKey = null; private final Lock lock = new ReentrantLock(); protected JsonParser parser = new JsonParser(); private ScheduledFuture<?> listeningJob; private ScheduledFuture<?> pollingJob; private int maxPresetCurrent = 0; private int maxSystemCurrent = 63000; public KeContactP20Handler(Thing thing) { super(thing); } @Override public void initialize() { logger.debug("Initializing KEBA KeContact P20 handler."); try { selector = Selector.open(); } catch (IOException e) { logger.error("An exception occurred while registering the selector: '{}'", e.getMessage()); } configureListener(LISTENER_PORT_NUMBER); if (getConfig().get(IP_ADDRESS) != null && !getConfig().get(IP_ADDRESS).equals("")) { establishConnection(); if (listeningJob == null || listeningJob.isCancelled()) { try { listeningJob = scheduler.scheduleWithFixedDelay(listeningRunnable, 0, CONNECTION_REFRESH_INTERVAL, TimeUnit.MILLISECONDS); } catch (Exception e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "An exception occurred while scheduling the connection job"); } } if (pollingJob == null || pollingJob.isCancelled()) { try { pollingJob = scheduler.scheduleWithFixedDelay(pollingRunnable, 0, ((BigDecimal) getConfig().get(POLLING_REFRESH_INTERVAL)).intValue(), TimeUnit.SECONDS); } catch (Exception e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "An exception occurred while scheduling the polling job"); } } } else { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "IP address or port number not set"); } } @Override public void dispose() { try { selector.close(); } catch (IOException e) { logger.error("An exception occurred while closing the selector: '{}'", e.getMessage()); } try { datagramChannel.close(); } catch (IOException e) { logger.warn("An exception occurred while closing the channel '{}': {}", datagramChannel, e.getMessage()); } try { listenerChannel.close(); } catch (IOException e) { logger.error("An exception occurred while closing the listener channel on port number {} ({})", LISTENER_PORT_NUMBER, e.getMessage()); } if (listeningJob != null && !listeningJob.isCancelled()) { listeningJob.cancel(true); listeningJob = null; } if (pollingJob != null && !pollingJob.isCancelled()) { pollingJob.cancel(true); pollingJob = null; } logger.debug("Handler disposed."); } protected void configureListener(int listenerPort) { // open the listener port try { listenerChannel = DatagramChannel.open(); listenerChannel.socket().bind(new InetSocketAddress(listenerPort)); listenerChannel.configureBlocking(false); logger.info("Listening for incoming data on {}", listenerChannel.getLocalAddress()); synchronized (selector) { selector.wakeup(); try { listenerKey = listenerChannel.register(selector, listenerChannel.validOps()); } catch (ClosedChannelException e1) { logger.error("An exception occurred while registering a selector: {}", e1.getMessage()); } } } catch (Exception e2) { logger.error("An exception occurred while creating the Listener Channel on port number {} ({})", listenerPort, e2.getMessage()); } } protected ByteBuffer onReadable(DatagramChannel theChannel, int bufferSize, InetAddress permittedClientAddress) { lock.lock(); try { SelectionKey theSelectionKey = theChannel.keyFor(selector); if (theSelectionKey != null) { synchronized (selector) { try { selector.selectNow(); } catch (IOException e) { logger.error("An exception occurred while selecting: {}", e.getMessage()); } } Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while (it.hasNext()) { SelectionKey selKey = it.next(); it.remove(); if (selKey.isValid() && selKey.isReadable() && selKey == theSelectionKey) { ByteBuffer readBuffer = ByteBuffer.allocate(bufferSize); int numberBytesRead = 0; boolean error = false; if (selKey == listenerKey) { try { InetSocketAddress clientAddress = (InetSocketAddress) theChannel.receive(readBuffer); if (clientAddress.getAddress().equals(permittedClientAddress)) { logger.debug("Received {} on the listener port from {}", new String(readBuffer.array()), clientAddress); numberBytesRead = readBuffer.position(); } else { logger.warn( "Received data from '{}' which is not the permitted remote address '{}'", clientAddress, permittedClientAddress); return null; } } catch (Exception e) { logger.error("An exception occurred while receiving data on the listener port: '{}'", e.getMessage()); error = true; } } else { try { numberBytesRead = theChannel.read(readBuffer); } catch (NotYetConnectedException e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "The remote host is not yet connected"); error = true; } catch (PortUnreachableException e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "The remote host is probably not a KEBA EV Charging station"); error = true; } catch (IOException e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "An IO exception occurred"); error = true; } } if (numberBytesRead == -1) { error = true; } if (error) { logger.debug("Disconnecting '{}' because of a socket error", getThing().getUID().toString()); try { theChannel.close(); } catch (IOException e) { logger.error("An exception occurred while closing the channel '{}': {}", datagramChannel, e.getMessage()); } onConnectionLost(); } else { readBuffer.flip(); return readBuffer; } } } } return null; } finally { lock.unlock(); } } protected void onWritable(ByteBuffer buffer, DatagramChannel theChannel) { lock.lock(); try { SelectionKey theSelectionKey = theChannel.keyFor(selector); if (theSelectionKey != null) { synchronized (selector) { try { selector.selectNow(); } catch (IOException e) { logger.error("An exception occurred while selecting: {}", e.getMessage()); } } Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while (it.hasNext()) { SelectionKey selKey = it.next(); it.remove(); if (selKey.isValid() && selKey.isWritable() && selKey == theSelectionKey) { boolean error = false; buffer.rewind(); try { logger.debug("Sending '{}' on the channel '{}'->'{}'", new Object[] { new String(buffer.array()), theChannel.getLocalAddress(), theChannel.getRemoteAddress() }); theChannel.write(buffer); } catch (NotYetConnectedException e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "The remote host is not yet connected"); error = true; } catch (ClosedChannelException e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "The connection to the remote host is closed"); error = true; } catch (IOException e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "An IO exception occurred"); error = true; } if (error) { logger.debug("Disconnecting '{}' because of a socket error", getThing().getUID().toString()); try { theChannel.close(); } catch (IOException e) { logger.warn("An exception occurred while closing the channel '{}': {}", datagramChannel, e.getMessage()); } onConnectionLost(); } } } } } finally { lock.unlock(); } } public void onConnectionLost() { establishConnection(); } public void onConnectionResumed() { updateStatus(ThingStatus.ONLINE); } private void establishConnection() { lock.lock(); try { if (getThing().getStatusInfo().getStatusDetail() != ThingStatusDetail.CONFIGURATION_ERROR && getConfig().get(IP_ADDRESS) != null && !getConfig().get(IP_ADDRESS).equals("")) { try { datagramChannel = DatagramChannel.open(); } catch (Exception e2) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "An exception occurred while opening a datagram channel"); } try { datagramChannel.configureBlocking(false); } catch (IOException e2) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "An exception occurred while configuring a datagram channel"); } synchronized (selector) { selector.wakeup(); int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE; try { datagramChannelKey = datagramChannel.register(selector, interestSet); } catch (ClosedChannelException e1) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "An exception occurred while registering a selector"); } InetSocketAddress remoteAddress = new InetSocketAddress((String) getConfig().get(IP_ADDRESS), REMOTE_PORT_NUMBER); try { logger.trace("Connecting the channel for {} ", remoteAddress); datagramChannel.connect(remoteAddress); onConnectionResumed(); } catch (Exception e) { logger.error("An exception occurred while connecting connecting to '{}:{}' : {}", new Object[] { (String) getConfig().get(IP_ADDRESS), REMOTE_PORT_NUMBER, e.getMessage() }); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "An exception occurred while connecting"); } } } else { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, getThing().getStatusInfo().getDescription()); } } finally { lock.unlock(); } } private Runnable listeningRunnable = new Runnable() { @Override public void run() { lock.lock(); try { if (getThing().getStatusInfo().getStatusDetail() != ThingStatusDetail.CONFIGURATION_ERROR) { if (datagramChannel == null || !datagramChannel.isConnected()) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "The connection is not yet initialized"); onConnectionLost(); } if (datagramChannel.isConnected()) { long stamp = System.currentTimeMillis(); if (!InetAddress.getByName(((String) getConfig().get(IP_ADDRESS))).isReachable(PING_TIME_OUT)) { logger.debug("Ping timed out after '{}' milliseconds", System.currentTimeMillis() - stamp); logger.trace("Disconnecting the datagram channel '{}'", datagramChannel); try { datagramChannel.close(); } catch (IOException e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "An exception occurred while closing the channel"); } updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "A ping timeout occurred"); onConnectionLost(); } else { ByteBuffer buffer = onReadable(datagramChannel, BUFFER_SIZE, null); if (buffer != null && buffer.remaining() > 0) { onRead(buffer, datagramChannel); } } } ByteBuffer buffer = onReadable(listenerChannel, BUFFER_SIZE, InetAddress.getByName((String) getConfig().get(IP_ADDRESS))); if (buffer != null && buffer.remaining() > 0) { onRead(buffer, listenerChannel); } } } catch (Exception e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "An exception occurred while receiving event data from the charging station"); } finally { lock.unlock(); } } }; private Runnable pollingRunnable = new Runnable() { @Override public void run() { try { String command = "report 1"; ByteBuffer byteBuffer = ByteBuffer.allocate(command.getBytes().length); try { byteBuffer.put(command.getBytes("ASCII")); onWritable(byteBuffer, datagramChannel); } catch (UnsupportedEncodingException | NumberFormatException e) { logger.error("An exception occurred while polling the KEBA KeContact P20 for '{}': {}", getThing().getUID(), e.getMessage()); } Thread.sleep(REPORT_INTERVAL); command = "report 2"; byteBuffer = ByteBuffer.allocate(command.getBytes().length); try { byteBuffer.put(command.getBytes("ASCII")); onWritable(byteBuffer, datagramChannel); } catch (UnsupportedEncodingException | NumberFormatException e) { logger.error("An exception occurred while polling the KEBA KeContact P20 for '{}': {}", getThing().getUID(), e.getMessage()); } Thread.sleep(REPORT_INTERVAL); command = "report 3"; byteBuffer = ByteBuffer.allocate(command.getBytes().length); try { byteBuffer.put(command.getBytes("ASCII")); onWritable(byteBuffer, datagramChannel); } catch (UnsupportedEncodingException | NumberFormatException e) { logger.error("An exception occurred while polling the KEBA KeContact P20 for '{}': {}", getThing().getUID(), e.getMessage()); } } catch (Exception e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } } }; protected void onRead(ByteBuffer byteBuffer, DatagramChannel datagramChannel) { String response = new String(byteBuffer.array(), 0, byteBuffer.limit()); response = StringUtils.chomp(response); if (response.contains("TCH-OK")) { // ignore confirmation messages which are not JSON return; } try { JsonObject readObject = parser.parse(response).getAsJsonObject(); for (Entry<String, JsonElement> entry : readObject.entrySet()) { switch (entry.getKey()) { case "Product": { Map<String, String> properties = editProperties(); properties.put(CHANNEL_MODEL, entry.getValue().getAsString()); updateProperties(properties); break; } case "Serial": { Map<String, String> properties = editProperties(); properties.put(CHANNEL_SERIAL, entry.getValue().getAsString()); updateProperties(properties); break; } case "Firmware": { Map<String, String> properties = editProperties(); properties.put(CHANNEL_FIRMWARE, entry.getValue().getAsString()); updateProperties(properties); break; } case "Plug": { int state = entry.getValue().getAsInt(); switch (state) { case 0: { updateState(new ChannelUID(getThing().getUID(), CHANNEL_WALLBOX), OnOffType.OFF); updateState(new ChannelUID(getThing().getUID(), CHANNEL_VEHICLE), OnOffType.OFF); updateState(new ChannelUID(getThing().getUID(), CHANNEL_PLUG_LOCKED), OnOffType.OFF); break; } case 1: { updateState(new ChannelUID(getThing().getUID(), CHANNEL_WALLBOX), OnOffType.ON); updateState(new ChannelUID(getThing().getUID(), CHANNEL_VEHICLE), OnOffType.OFF); updateState(new ChannelUID(getThing().getUID(), CHANNEL_PLUG_LOCKED), OnOffType.OFF); break; } case 3: { updateState(new ChannelUID(getThing().getUID(), CHANNEL_WALLBOX), OnOffType.ON); updateState(new ChannelUID(getThing().getUID(), CHANNEL_VEHICLE), OnOffType.OFF); updateState(new ChannelUID(getThing().getUID(), CHANNEL_PLUG_LOCKED), OnOffType.ON); break; } case 5: { updateState(new ChannelUID(getThing().getUID(), CHANNEL_WALLBOX), OnOffType.ON); updateState(new ChannelUID(getThing().getUID(), CHANNEL_VEHICLE), OnOffType.ON); updateState(new ChannelUID(getThing().getUID(), CHANNEL_PLUG_LOCKED), OnOffType.OFF); break; } case 7: { updateState(new ChannelUID(getThing().getUID(), CHANNEL_WALLBOX), OnOffType.ON); updateState(new ChannelUID(getThing().getUID(), CHANNEL_VEHICLE), OnOffType.ON); updateState(new ChannelUID(getThing().getUID(), CHANNEL_PLUG_LOCKED), OnOffType.ON); break; } } break; } case "State": { State newState = new DecimalType(entry.getValue().getAsInt()); updateState(new ChannelUID(getThing().getUID(), CHANNEL_STATE), newState); break; } case "Enable sys": { int state = entry.getValue().getAsInt(); switch (state) { case 1: { updateState(new ChannelUID(getThing().getUID(), CHANNEL_ENABLED), OnOffType.ON); break; } default: { updateState(new ChannelUID(getThing().getUID(), CHANNEL_ENABLED), OnOffType.OFF); break; } } break; } case "Curr HW": { int state = entry.getValue().getAsInt(); maxSystemCurrent = state; State newState = new DecimalType(state); updateState(new ChannelUID(getThing().getUID(), CHANNEL_MAX_SYSTEM_CURRENT), newState); if (maxSystemCurrent < maxPresetCurrent) { sendCommand("curr " + String.valueOf(maxSystemCurrent)); updateState(new ChannelUID(getThing().getUID(), CHANNEL_MAX_PRESET_CURRENT), new DecimalType(maxSystemCurrent)); updateState(new ChannelUID(getThing().getUID(), CHANNEL_MAX_PRESET_CURRENT_RANGE), new PercentType((maxSystemCurrent - 6000) * 100 / (maxSystemCurrent - 6000))); } break; } case "Curr user": { int state = entry.getValue().getAsInt(); maxPresetCurrent = state; updateState(new ChannelUID(getThing().getUID(), CHANNEL_MAX_PRESET_CURRENT), new DecimalType(state)); updateState(new ChannelUID(getThing().getUID(), CHANNEL_MAX_PRESET_CURRENT_RANGE), new PercentType((state - 6000) * 100 / (maxSystemCurrent - 6000))); break; } case "Curr FS": { int state = entry.getValue().getAsInt(); State newState = new DecimalType(state); updateState(new ChannelUID(getThing().getUID(), CHANNEL_FAILSAFE_CURRENT), newState); break; } case "Output": { int state = entry.getValue().getAsInt(); switch (state) { case 1: { updateState(new ChannelUID(getThing().getUID(), CHANNEL_OUTPUT), OnOffType.ON); break; } default: { updateState(new ChannelUID(getThing().getUID(), CHANNEL_OUTPUT), OnOffType.OFF); break; } } break; } case "Input": { int state = entry.getValue().getAsInt(); switch (state) { case 1: { updateState(new ChannelUID(getThing().getUID(), CHANNEL_INPUT), OnOffType.ON); break; } default: { updateState(new ChannelUID(getThing().getUID(), CHANNEL_INPUT), OnOffType.OFF); break; } } break; } case "Sec": { long state = entry.getValue().getAsLong(); Calendar uptime = Calendar.getInstance(); uptime.setTimeZone(TimeZone.getTimeZone("GMT")); uptime.setTimeInMillis(state * 1000); SimpleDateFormat pFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); pFormatter.setTimeZone(TimeZone.getTimeZone("GMT")); updateState(new ChannelUID(getThing().getUID(), CHANNEL_UPTIME), new DateTimeType(pFormatter.format(uptime.getTime()))); break; } case "U1": { int state = entry.getValue().getAsInt(); State newState = new DecimalType(state); updateState(new ChannelUID(getThing().getUID(), CHANNEL_U1), newState); break; } case "U2": { int state = entry.getValue().getAsInt(); State newState = new DecimalType(state); updateState(new ChannelUID(getThing().getUID(), CHANNEL_U2), newState); break; } case "U3": { int state = entry.getValue().getAsInt(); State newState = new DecimalType(state); updateState(new ChannelUID(getThing().getUID(), CHANNEL_U3), newState); break; } case "I1": { int state = entry.getValue().getAsInt(); State newState = new DecimalType(state / 1000); updateState(new ChannelUID(getThing().getUID(), CHANNEL_I1), newState); break; } case "I2": { int state = entry.getValue().getAsInt(); State newState = new DecimalType(state / 1000); updateState(new ChannelUID(getThing().getUID(), CHANNEL_I2), newState); break; } case "I3": { int state = entry.getValue().getAsInt(); State newState = new DecimalType(state / 1000); updateState(new ChannelUID(getThing().getUID(), CHANNEL_I3), newState); break; } case "P": { long state = entry.getValue().getAsLong(); State newState = new DecimalType(state / 1000); updateState(new ChannelUID(getThing().getUID(), CHANNEL_POWER), newState); break; } case "PF": { int state = entry.getValue().getAsInt(); State newState = new PercentType(state / 10); updateState(new ChannelUID(getThing().getUID(), CHANNEL_POWER_FACTOR), newState); break; } case "E pres": { long state = entry.getValue().getAsLong(); State newState = new DecimalType(state / 10); updateState(new ChannelUID(getThing().getUID(), CHANNEL_SESSION_CONSUMPTION), newState); break; } case "E total": { long state = entry.getValue().getAsLong(); State newState = new DecimalType(state / 10); updateState(new ChannelUID(getThing().getUID(), CHANNEL_TOTAL_CONSUMPTION), newState); break; } } } } catch (JsonParseException e) { logger.debug("Invalid JSON data will be ignored: '{}'", response); } } @Override public void handleCommand(ChannelUID channelUID, Command command) { if (command instanceof RefreshType) { // Refresh all channels by scheduling a single run of the polling runnable scheduler.schedule(pollingRunnable, 0, TimeUnit.SECONDS); } else { switch (channelUID.getId()) { case CHANNEL_MAX_PRESET_CURRENT: { if (command instanceof DecimalType) { sendCommand("curr " + String.valueOf( Math.min(Math.max(6000, ((DecimalType) command).intValue()), maxSystemCurrent))); } break; } case CHANNEL_MAX_PRESET_CURRENT_RANGE: { if (command instanceof OnOffType || command instanceof IncreaseDecreaseType || command instanceof PercentType) { int newValue = 6000; if (command == IncreaseDecreaseType.INCREASE) { newValue = Math.min(Math.max(6000, maxPresetCurrent + 1), maxSystemCurrent); } else if (command == IncreaseDecreaseType.DECREASE) { newValue = Math.min(Math.max(6000, maxPresetCurrent - 1), maxSystemCurrent); } else if (command == OnOffType.ON) { newValue = maxSystemCurrent; } else if (command == OnOffType.OFF) { newValue = 6000; } else if (command instanceof PercentType) { newValue = 6000 + (maxSystemCurrent - 6000) * ((PercentType) command).intValue() / 100; } else { return; } sendCommand("curr " + String.valueOf(newValue)); } break; } case CHANNEL_ENABLED: { if (command instanceof OnOffType) { if (command == OnOffType.ON) { sendCommand("ena 1"); } else if (command == OnOffType.OFF) { sendCommand("ena 0"); } else { return; } } break; } case CHANNEL_OUTPUT: { if (command instanceof OnOffType) { if (command == OnOffType.ON) { sendCommand("output 1"); } else if (command == OnOffType.OFF) { sendCommand("output 0"); } else { return; } } break; } } } } private void sendCommand(String command) { if (command != null) { ByteBuffer byteBuffer = ByteBuffer.allocate(command.getBytes().length); try { byteBuffer.put(command.getBytes("ASCII")); onWritable(byteBuffer, datagramChannel); } catch (UnsupportedEncodingException | NumberFormatException e) { logger.error("An exception occurred while sending a command to the KeContact wallbox for '{}': {}", getThing().getUID(), e.getMessage()); } } } }